﻿using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Net;
using System.Net.Sockets;
using System.Speech.Synthesis;
using System.Threading;
using System.Windows.Forms;
using SongsDB;

//New BSD License

namespace MMVoiceComm
{
    public class MMStart
    {
        //MediaMonkey Objects, it's global, but it works
        public static SDBApplication SDB;

        //Configuration Manager
        private ConfigurationManager ConfigMan = new ConfigurationManager();
        private VoiceCommander MMVoiceCommander = new VoiceCommander();
        private MusicDatabaseHandler MusicDB = new MusicDatabaseHandler();
        public static SDBMenuItem activeButton;
        private int reinit = 0;
        private static bool isInInit = true;

        private UdpClient udpListener = new UdpClient(6589);

        public enum networkMessages
        {
            reinitAll = 1,
            configChanged,
            grammarChanged
        }

        private Boolean sortIncoming;

        public void Init(SDBApplication sdbA)
        {
            //Make it global
            SDB = sdbA;

            if (SDB.IsRunning)
            {
                //Fork the process so MediaMonkey doesn't sit on the load screen forever
                new Thread(Init2).Start();
            }
        }

        private void Init2()
        {
            try
            {


                InitConfig();
                InitGrammar();
                InitGUI();
                InitNetwork();
                isInInit = false;
            }
            catch (Exception e)
            {
                Logger.log("Oh No Error in MMVoiceComm Load!\r\n\n" + e.Message + "\r\n" + e.StackTrace);
            }
        }

        private void InitNetwork()
        {
            if (udpListener != null)
            {
                udpListener.Close();
                
            }

            udpListener = new UdpClient(6589);
           // udpListener.ExclusiveAddressUse = false;
            udpListener.BeginReceive(udpGotCommand, null);
        }

        private void udpGotCommand(IAsyncResult ar)
        {
            if (!isInInit)
            {
                IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);

                if (udpListener != null)
                {
                    byte[] data = udpListener.EndReceive(ar, ref remote);

                    if (remote.Address.ToString().Equals("127.0.0.1"))
                    {

                        if (data[0].Equals(Convert.ToByte(networkMessages.reinitAll)))
                        {
                            udpListener.Close();
                            this.ReInit();
                        }
                        else
                        {
                            //Others not supported yet
                        }
                    }
                    else
                    {
                        //Not local ignore it
                    }
                }
                else
                {
                    //Aww poo someone reinited first
                }

                if (udpListener != null) udpListener.BeginReceive(udpGotCommand, null);
            }
        }


        private void ReInit()
        {
            try
            {
                //Keeps a lock on initilization
                if (!isInInit)
                {
                    isInInit = true;
                    reinit++;
                    Logger.log("ReIniting " + reinit);

                    SDB.OnNowPlayingModified -= new ISDBApplicationEvents_OnNowPlayingModifiedEventHandler(SDB_OnNowPlayingModified);
                    SDB.OnNowPlayingSelectionChanged -= new ISDBApplicationEvents_OnNowPlayingSelectionChangedEventHandler(SDB_OnNowPlayingSelectionChanged);

                    activeButton.Visible = false;
                    //Darn GC keeps it around and ticking
                    MMVoiceCommander.TerminateAllBonds();
                    //MMVoiceCommander = null;
                    //ConfigMan = null;
                    
                    // MusicDB = null;
                    ConfigMan.Reload();
                    //MMVoiceCommander = new VoiceCommander();
                    InitConfig();
                    Logger.log("ReInitedConfig " + reinit);
                    InitGrammar();
                    Logger.log("ReInited Grammar " + reinit);
                    InitGUI();
                    Logger.log("ReInited GUI " + reinit);
                    InitNetwork();
                    Logger.log("ReInited Network "+reinit);
                    isInInit = false;
                }
            }
            catch (Exception e)
            {
                Logger.log("Oh No Error in MMVoiceComm ReInit!\r\n\n" + e.Message + "\r\n" + e.StackTrace);
            }
        }

        private void InitConfig()
        {
            //Well that was easy
            MMVoiceCommander.SpeechCommands = ConfigMan.Commands;

            //Use binary or linear search?
            if (ConfigMan.GetConfigValue("binarySearch").ToLower().Equals("false"))
                sortIncoming = false;


            if (ConfigMan.GetConfigValue("voice").Length > 0)
                MMVoiceCommander.SetVoice(ConfigMan.GetConfigValue("voice"));

            if (ConfigMan.GetConfigValue("synthRepeatCommand").ToLower().Equals("true"))
                MMVoiceCommander.synthRepeatCommand = true;

            if (ConfigMan.GetConfigValue("autoAddSongs").ToLower().Equals("true"))
                MMVoiceCommander.autoAddSongs = true;

            if (ConfigMan.GetConfigValue("voiceToggleListen").ToLower().Equals("false"))
                MMVoiceCommander.startStopEnabled = false;

            //Jumps
            int jumpMax = 20;
            int jumpMin = 1;
            //makes sure we have a maximum distance
            if (!ConfigMan.GetConfigValue("jumpDistanceMax").Equals(""))
                try
                {
                    jumpMax = Convert.ToInt32(ConfigMan.GetConfigValue("jumpDistanceMax"));
                }
                catch (Exception e) { }
            //makes sure we have a minimum distance
            if (!ConfigMan.GetConfigValue("jumpDistanceMin").Equals(""))
                try
                {
                    jumpMin = Convert.ToInt32(ConfigMan.GetConfigValue("jumpDistanceMin"));
                }
                catch (Exception e) { }
            if (jumpMin < jumpMax && jumpMin > 0)
            {
                //Make our numbers sequence
                MMVoiceCommander.generateNumbers(jumpMin, jumpMax);
            }
        }

        private void InitGUI()
        {
            if (activeButton != null) activeButton = null;
            //Nothing to do here...
            activeButton = SDB.UI.AddMenuItem(SDB.UI.Menu_TbStandard, 0, 0);
            activeButton.IconIndex = 51;
            activeButton.Caption = "Listening";
            activeButton.Visible = true;

        }

        public void InitGrammar()
        {
            MusicDB.Connect();
            //used to calculate time to load.
            DateTime f = DateTime.Now;

            //Get a collection of all the artists, apparently this SQLite lib
            // doesn't recognize DISTINCT and we must check for duplicates ourselves
            SQLiteDataReader sqlR = MusicDB.ExecuteSelect("Artist", "Songs");
            AddToListFromReader(MMVoiceCommander.artistsList, sqlR);
            sqlR.Close();

            //Get all albums
            sqlR = MusicDB.ExecuteSelect("Album", "Songs");
            AddToListFromReader(MMVoiceCommander.albumsList, sqlR);
            sqlR.Close();

            //Get a collection of all the titles
            sqlR = MusicDB.ExecuteSelect("SongTitle", "Songs");
            AddToListFromReader(MMVoiceCommander.songsList, sqlR);
            sqlR.Close();

            //Get a collection of all the genres
            sqlR = MusicDB.ExecuteSelect("Genre", "Songs");
            AddToListFromReader(MMVoiceCommander.genresList, sqlR);
            sqlR.Close();

            //Get all the playlists
            SDBPlaylist root = SDB.get_PlaylistByTitle("");
            for (int i = 0; i < root.ChildPlaylists.Count; i++)
            {
                if (root.ChildPlaylists.get_Item(i).Title.Length > 0) MMVoiceCommander.playlistList.Add(root.ChildPlaylists.get_Item(i).Title);
            }



            //Build grammars for recognizing all the commands
            MMVoiceCommander.BuildDefaultGrammars();

            //Tell the user we are ready to roll
            //MMVoiceCommander.SpeakAsync("Loading Took "+((int)DateTime.Now.Subtract(f).TotalSeconds)+" Seconds. I Am Now Ready For Voice Commands");
            MMVoiceCommander.SetupRecognition();
            //Start actually listening
            MMVoiceCommander.StartListening();

            //Start with a working Jump To Song...
            MMVoiceCommander.UnloadCurrentJumpToSongGrammar();
            MMVoiceCommander.BuildJumpToSongGrammar();

            SDB.OnNowPlayingModified += new ISDBApplicationEvents_OnNowPlayingModifiedEventHandler(SDB_OnNowPlayingModified);
            SDB.OnNowPlayingSelectionChanged += new ISDBApplicationEvents_OnNowPlayingSelectionChangedEventHandler(SDB_OnNowPlayingSelectionChanged);
        }

        void SDB_OnNowPlayingSelectionChanged()
        {
        }

        void SDB_OnNowPlayingModified()
        {
            MMVoiceCommander.UnloadCurrentJumpToSongGrammar();
            MMVoiceCommander.BuildJumpToSongGrammar();
        }

        //Takes a reader and reads all of the first column into a list
        public void AddToListFromReader(List<string> list, SQLiteDataReader SQLdr)
        {
            if (sortIncoming)
            {
                //Must sort first, or else preliminary binary search will not work
                list.Sort();
                string tmp;
                while (SQLdr.Read())
                {
                    tmp = SQLdr.GetString(0);
                    if (list.BinarySearch(tmp) < 0)
                    {
                        if (tmp.Trim().Length > 0)
                        {
                            list.Add(tmp);
                            list.Sort();
                        }
                    }
                } //End While
            }
            else
            {
                //Linear search
                string tmp;
                while (SQLdr.Read())
                {
                    tmp = SQLdr.GetString(0);
                    if (!list.Contains(tmp))
                    {
                        if (tmp.Trim().Length > 0)
                        {
                            list.Add(tmp);
                        }
                    }
                }
            }
        }
    }
}